package com.googlecode.cswish.util;

import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;

import javax.annotation.Resource;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.views.freemarker.FreemarkerManager;
import org.springframework.stereotype.Service;

import com.googlecode.cswish.struts.spring.ObjectFactory;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.util.ValueStack;

import freemarker.cache.CacheStorage;
import freemarker.cache.SoftCacheStorage;
import freemarker.cache.TemplateCache;
import freemarker.cache.TemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.SimpleHash;
import freemarker.template.Template;

@Service
public class FreemarkerDStringTplEngine {

	private static final Logger logger = Logger.getLogger(FreemarkerDStringTplEngine.class);

	@Inject
	private FreemarkerManager freemarkerManager;
	
	@Resource
	private ObjectFactory objectFactory;
	
	private TemplateCache cache;
	
	private Map<String, StringTemplateBuilder> builders = new HashMap<String, FreemarkerDStringTplEngine.StringTemplateBuilder>();
	
	private FreemarkerManager getFreemarkerManager() {
		if (freemarkerManager != null) {
			return freemarkerManager;
		}
		
		synchronized (FreemarkerDStringTplEngine.class) {
			if (freemarkerManager != null) {
				return freemarkerManager;
			}
			
			objectFactory.injectInternalBeans(this);
			return freemarkerManager;
		}
	}
	
	private TemplateCache getCache() {
		if (cache != null) {
			return cache;
		}
		
		synchronized (FreemarkerDStringTplEngine.class) {
			if (cache != null) {
				return cache;
			}
			
			StringTemplateLoader loader = new StringTemplateLoader();
			cache = new TemplateCache(loader);
			
			cache.setLocalizedLookup(false);
			ValueStack stack = ActionContext.getContext().getValueStack();
			Map context = stack.getContext();
			ServletContext servletContext = (ServletContext) context.get(ServletActionContext.SERVLET_CONTEXT);
			Configuration config = getFreemarkerManager().getConfiguration(servletContext);
			cache.setConfiguration(config);
			return cache;
		}
	}
	
	public interface StringTemplateBuilder {
		StringTemplateResource create(String name);
	}
	
	public static class StringTemplateResource {
		private String name;
		private String content;
		private File[] source;
		private long lastCheckTime;
		
		public String getName() {
			return name;
		}
		public void setName(String name) {
			this.name = name;
		}

		public String getContent() {
			return content;
		}
		public void setContent(String content) {
			this.content = content;
		}

		public File[] getSource() {
			return source;
		}
		public void setSource(File[] source) {
			this.source = source;
		}

		public long getLastCheckTime() {
			return lastCheckTime;
		}
		public void setLastCheckTime(long lastCheckTime) {
			this.lastCheckTime = lastCheckTime;
		}

		@Override
		public boolean equals(Object another) {
			if (another instanceof StringTemplateResource) {
				StringTemplateResource anotherResource = (StringTemplateResource) another;
				return this.content == null && anotherResource.content == null || this.content != null
				&& this.content.equals(anotherResource.content);
			}
			return false;
		}
	}
	
	public boolean hasBuilder(String builderName) {
		return builders.containsKey(builderName);
	}
	
	public void registerBuilder(String builderName, StringTemplateBuilder builder) {
		builders.put(builderName, builder);
	}
	
	
	class StringTemplateLoader implements TemplateLoader {

		private CacheStorage cacheStorage = new SoftCacheStorage();

		@Override
		public Object findTemplateSource(String name) throws IOException {
			StringTemplateResource resource = (StringTemplateResource) cacheStorage.get(name);
			if (resource != null) {
				if (resource.source == null) {
					return resource;
				} else {
					boolean found = false;
					for (File file : resource.source) {
						if (file != null && file.exists() && file.lastModified() <= resource.lastCheckTime) {
							found = true;
							break;
						}
					}
					if (found) {
						return resource;
					}
				}
			}

			int index = name.indexOf('\n');
			String builderName = name.substring(0, index);
			// the builder is registered by Call & Include, they defined the detailed creation method
			// see CallStringTemplateBuilder and IncludeStringTemplateBuilder
			StringTemplateResource tplResource = builders.get(builderName).create(name.substring(index + 1));
			
			cacheStorage.put(name, tplResource);
			return tplResource;
		}

		@Override
		public long getLastModified(Object templateSource) {
			StringTemplateResource source = (StringTemplateResource) templateSource;
			if (source.source != null) {
				long lastModified = 0;
				for (File file : source.source) {
					if (file != null) {
						long newModified = file.lastModified();
						if (newModified > lastModified) {
							lastModified = newModified;
						}
					}
				}
				return lastModified;
			} else {
				return 0;
			}
		}

		@Override
		public Reader getReader(Object templateSource, String encoding) throws IOException {
			StringTemplateResource source = (StringTemplateResource) templateSource;
			return new StringReader(source.content);
		}

		@Override
		public void closeTemplateSource(Object templateSource) throws IOException {
		}

	}
	
	public Template getTemplate(String builderName, String key) {
		Configuration configuration = getFreemarkerManager().getConfiguration(null);
		Template template = null;
		try {
			// see StringTemplateLoader.findTemplateSource
			template = getCache().getTemplate(builderName + '\n' + key, configuration.getLocale(), 
					configuration.getEncoding(configuration.getLocale()), true);
		} catch (Exception e) {
			logger.error("error when rendering", e);
        }
		return template;
	}
	
	public void renderTemplate( Writer writer, ValueStack stack, Template template) throws Exception {
    	// get the various items required from the stack
        Map context = stack.getContext();
        ServletContext servletContext = (ServletContext) context.get(ServletActionContext.SERVLET_CONTEXT);
        HttpServletRequest req = (HttpServletRequest) context.get(ServletActionContext.HTTP_REQUEST);
        HttpServletResponse res = (HttpServletResponse) context.get(ServletActionContext.HTTP_RESPONSE);

        // prepare freemarker
        Configuration config = getFreemarkerManager().getConfiguration(servletContext);
        
        ActionInvocation ai = ActionContext.getContext().getActionInvocation();

        Object action = (ai == null) ? null : ai.getAction();
        SimpleHash model = getFreemarkerManager().buildTemplateModel(stack, action,
        		servletContext, req, res, config.getObjectWrapper());

        model.put("tag", this);
        model.put("themeProperties", new HashMap(0));

        try {
            stack.push(this);
            template.process(model, writer);
        } finally {
            stack.pop();
        }
    }
}
